﻿Imports System.Reflection
Imports System.Text
Imports System.Runtime.Serialization
Imports System.Runtime.Serialization.Formatters.Binary
Imports System.IO
Imports System.Xml.Serialization
Imports System.Globalization

Public Module ReflectionHelper
    Public Function getInstance(Of T)() As T
        Try
            Return CType(Activator.CreateInstance(GetType(T)), T)
        Catch ex As Exception
            Throw ex
        End Try
    End Function

    Public Function getTypeNullable(ByVal T As Type) As Type

        If (Not T Is Nothing) AndAlso IsNullableType(T) Then
            getTypeNullable = Nullable.GetUnderlyingType(T)

        Else
            getTypeNullable = T
        End If
    End Function


    Public Function Deserialize(ByVal filetoOpen As String) As Object
        Try
            ' Cryptography.Crypto.DecryptFile(filetoOpen)
            Using stream As IO.Stream = File.Open(filetoOpen, FileMode.Open)
                Dim bin As New BinaryFormatter()
                Return bin.Deserialize(stream)
            End Using
        Catch ex As Exception

            File.Delete(filetoOpen)
        End Try
        Return Nothing
    End Function

    Public Sub Serialize(ByVal filetoOpen As String, ByVal value As Object)
        Try
            Using stream As Stream = File.Open(filetoOpen, FileMode.Create)
                Dim bin As New BinaryFormatter()
                bin.Serialize(stream, value)
            End Using
            ' Cryptography.Crypto.EncryptFile(filetoOpen)
        Catch ex As Exception
            Throw ex
        End Try
    End Sub

    Public Function XMLSerialize(ByVal source As Object) As String
        Dim serializer As New XmlSerializer(source.[GetType]())

        Using writer As New StringWriter()
            serializer.Serialize(writer, source)

            Return writer.ToString()
        End Using
        'If Not source.GetType().IsSerializable Then
        '    Dim o = Clone(CObj(source))
        '    Return o

        '    Throw New ArgumentException("The type must be serializable.", "source")
        'End If

        '' Don't serialize a null object, simply return the default for that object
        'If [Object].ReferenceEquals(source, Nothing) Then
        '    Return Nothing
        'End If

        'Dim formatter As IFormatter = New BinaryFormatter()
        'Dim stream As IO.Stream = New MemoryStream()
        'Using stream
        '    formatter.Serialize(stream, source)
        '    stream.Seek(0, SeekOrigin.Begin)
        '    Return stream.ToString()
        'End Using
    End Function

    Public Function Clone(Of T)(ByVal source As T) As T
        If Not GetType(T).IsSerializable Then
            Dim o = Clone(CObj(source))
            Return o

            Throw New ArgumentException("The type must be serializable.", "source")
        End If

        ' Don't serialize a null object, simply return the default for that object
        If [Object].ReferenceEquals(source, Nothing) Then
            Return Nothing
        End If

        Dim formatter As IFormatter = New BinaryFormatter()
        Dim stream As IO.Stream = New MemoryStream()
        Using stream
            formatter.Serialize(stream, source)
            stream.Seek(0, SeekOrigin.Begin)
            Return DirectCast(formatter.Deserialize(stream), T)
        End Using
    End Function



    Private Function Clone(ByVal objToClone As Object) As Object
        'First we create an instance of this specific type.
        Dim newObject As Object = Activator.CreateInstance(objToClone.[GetType]())

        'We get the array of fields for the new type instance.
        Dim fields As FieldInfo() = newObject.[GetType]().GetFields()

        Dim i As Integer = 0

        For Each fi As FieldInfo In objToClone.[GetType]().GetFields()
            'We query if the fiels support the ICloneable interface.
            Dim ICloneType As Type = fi.FieldType.GetInterface("ICloneable", True)

            If ICloneType IsNot Nothing Then
                'Getting the ICloneable interface from the object.
                Dim IClone As ICloneable = DirectCast(fi.GetValue(objToClone), ICloneable)

                'We use the clone method to set the new value to the field.
                fields(i).SetValue(newObject, IClone.Clone())
            Else
                ' If the field doesn't support the ICloneable 
                ' interface then just set it.
                fields(i).SetValue(newObject, fi.GetValue(objToClone))
            End If

            'Now we check if the object support the 
            'IEnumerable interface, so if it does
            'we need to enumerate all its items and check if 
            'they support the ICloneable interface.
            Dim IEnumerableType As Type = fi.FieldType.GetInterface("IEnumerable", True)
            If IEnumerableType IsNot Nothing Then
                'Get the IEnumerable interface from the field.
                Dim IEnum As IEnumerable = DirectCast(fi.GetValue(objToClone), IEnumerable)

                'This version support the IList and the 
                'IDictionary interfaces to iterate on collections.
                Dim IListType As Type = fields(i).FieldType.GetInterface("IList", True)
                Dim IDicType As Type = fields(i).FieldType.GetInterface("IDictionary", True)

                Dim j As Integer = 0
                If IListType IsNot Nothing Then
                    'Getting the IList interface.
                    Dim list As IList = DirectCast(fields(i).GetValue(newObject), IList)

                    For Each obj As Object In IEnum
                        'Checking to see if the current item 
                        'support the ICloneable interface.
                        ICloneType = obj.[GetType]().GetInterface("ICloneable", True)

                        If ICloneType IsNot Nothing Then
                            'If it does support the ICloneable interface, 
                            'we use it to set the clone of
                            'the object in the list.
                            Dim clone__1 As ICloneable = DirectCast(obj, ICloneable)

                            list(j) = clone__1.Clone()
                        End If

                        'NOTE: If the item in the list is not 
                        'support the ICloneable interface then in the 
                        'cloned list this item will be the same 
                        'item as in the original list
                        '(as long as this type is a reference type).

                        j += 1
                    Next
                ElseIf IDicType IsNot Nothing Then
                    'Getting the dictionary interface.
                    Dim dic As IDictionary = DirectCast(fields(i).GetValue(newObject), IDictionary)
                    j = 0

                    For Each de As DictionaryEntry In IEnum
                        'Checking to see if the item 
                        'support the ICloneable interface.
                        ICloneType = de.Value.[GetType]().GetInterface("ICloneable", True)

                        If ICloneType IsNot Nothing Then
                            Dim clone__1 As ICloneable = DirectCast(de.Value, ICloneable)

                            dic(de.Key) = clone__1.Clone()
                        End If
                        j += 1
                    Next
                End If
            End If
            i += 1
        Next
        Return newObject
    End Function

    Public Function getPropertyValue(ByVal obj As Object, ByVal propName As String) As Object
        Dim rs = obj.GetType().GetProperties().Where(Function(x) x.CanWrite AndAlso x.CanRead AndAlso x.Name.Equals(propName, StringComparison.InvariantCultureIgnoreCase))
        If rs.Any() Then
            Return rs.FirstOrDefault().GetValue(obj, Nothing)
        Else
            Return Nothing
        End If
    End Function
    Public Function ConvertStringArrayToString(ByVal array As String()) As String
        '
        ' Concatenate all the elements into a StringBuilder.
        '
        Dim builder As New StringBuilder()
        For Each value As String In array
            builder.Append(value)
            builder.Append("."c)
        Next
        Return builder.ToString()
    End Function

    Public Function getAllPropertiesAsString(ByVal ob As Object, ByVal tipoConexion As Integer, ByVal ParamArray exceptions() As String) As String

        Dim key As String = "getAllPropertiesAsString." & ob.GetType().FullName & "." & tipoConexion & "." & ConvertStringArrayToString(exceptions)

        Dim value = CacheMannager.getFromCache(Of Object, String)(key)
        If Not String.IsNullOrEmpty(value) Then
            Return value
        End If

        Dim sb As New StringBuilder()

        If exceptions Is Nothing OrElse Not exceptions.Any() Then
            For Each p In ob.GetType().GetProperties().Where(Function(x) x.CanRead AndAlso x.CanWrite)
                Select Case tipoConexion
                    Case 1
                        sb.AppendFormat("[{0}],", p.Name)
                    Case 3
                        sb.AppendFormat("{0},", p.Name)
                    Case Else
                        sb.AppendFormat("{0},", p.Name)

                End Select
            Next
        Else
            For Each p In ob.GetType().GetProperties().Where(Function(x) x.CanRead AndAlso x.CanWrite AndAlso Not exceptions.Contains(x.Name))
                Select Case tipoConexion
                    Case 1
                        sb.AppendFormat("[{0}],", p.Name)
                    Case 3
                        sb.AppendFormat("{0},", p.Name)
                    Case Else
                        sb.AppendFormat("{0},", p.Name)

                End Select
            Next
        End If


        If sb.Length > 0 Then
            Dim v = sb.Remove(sb.Length - 1, 1).ToString()
            CacheMannager.SetInCache(Of Object, String)(key, v, 500)
            Return v
        End If

        Return String.Empty
    End Function



    Public Function MapEntityFromDR(Of TEntityClass)(ByVal dr As DataRow) As TEntityClass
        Try
            Dim entityProperties = GetType(TEntityClass).GetProperties()
            Dim ent As TEntityClass = getInstance(Of TEntityClass)()

            Dim colNamesCollection As New Dictionary(Of String, String)
            For Each item As DataColumn In dr.Table.Columns
                colNamesCollection.Add(item.ColumnName, item.ColumnName.Replace(" "c, "_"))
            Next


            For Each p As PropertyInfo In entityProperties.Where(Function(x) x.CanWrite = True)
                Dim propertyName As String = p.Name

                Dim cNameList = colNamesCollection.Where(Function(x) x.Value.Equals(propertyName, StringComparison.InvariantCultureIgnoreCase))
                If cNameList.Any() Then
                    Dim colName = cNameList.FirstOrDefault().Key
                    SetEntityValue(CObj(ent), p.Name, IIf(dr(colName) Is DBNull.Value, Nothing, dr(colName)))
                End If

            Next
            Return ent
        Catch ex As Exception
            Throw ex
        End Try
    End Function

    Public Function IsNullableType(ByVal myType As Type) As Boolean
        Return myType IsNot Nothing AndAlso (myType.IsGenericType) AndAlso (myType.GetGenericTypeDefinition() Is GetType(Nullable(Of )))
    End Function
    Public Function getValueFromNullable(ByVal value As Object) As Object
        If (Not value Is Nothing) AndAlso IsNullableType(value.GetType()) Then
            Dim UnderlyingType As Type = Nullable.GetUnderlyingType(value.GetType())
            Return Convert.ChangeType(value, UnderlyingType)
        Else
            Return value
        End If
    End Function

    Public Function gettypeFromNullable(ByVal t As Type) As Type
        If IsNullableType(t) Then
            Return Nullable.GetUnderlyingType(t)
        Else

            Return t
        End If
    End Function

    Public Function MapEntityFromDR(ByVal dr As DataRow, ByVal objInstance As Object) As Object
        Try
            Dim entityProperties = objInstance.GetType().GetProperties()

            For Each p As PropertyInfo In entityProperties.Where(Function(x) x.CanWrite = True)
                If dr.Table.Columns.Contains(p.Name) Then
                    SetEntityValue(CObj(objInstance), p.Name, IIf(dr(p.Name) Is DBNull.Value, Nothing, dr(p.Name)))
                End If
            Next
            Return objInstance
        Catch ex As Exception
            Throw ex
        End Try
        Return Nothing
    End Function


    Public Function MapEntityFromDS(Of TEntityClass)(ByVal ds As DataSet) As TEntityClass
        Try
            Dim entityProperties = GetType(TEntityClass).GetProperties()
            Dim ent As TEntityClass = getInstance(Of TEntityClass)()

            For Each p As PropertyInfo In entityProperties.Where(Function(x) x.CanWrite = True)
                If ds.Tables(0).Columns.Contains(p.Name) Then
                    SetEntityValue(CObj(ent), p.Name, ds.Tables(0).Rows(0)(p.Name))
                End If
            Next
            Return ent
        Catch ex As Exception
            Throw ex
        End Try
    End Function

    Public Function MapEntityFromDS(ByVal clsInstance As Object, ByVal ds As DataSet) As Object
        Try
            Dim entityProperties = clsInstance.GetType().GetProperties()

            For Each p As PropertyInfo In entityProperties.Where(Function(x) x.CanWrite = True)
                If ds.Tables(0).Columns.Contains(p.Name) AndAlso ds.Tables(0).Rows(0)(p.Name) IsNot DBNull.Value Then
                    SetEntityValue(CObj(clsInstance), p.Name, ds.Tables(0).Rows(0)(p.Name))
                End If
            Next
            Return clsInstance
        Catch ex As Exception

            Throw ex
        End Try
    End Function


    Public Function GetEntityValue(ByVal currentEntity As Object, ByVal propertyName As String) As Object
        Dim pi = currentEntity.GetType().GetProperties().Where(Function(x) x.Name.Equals(propertyName, StringComparison.InvariantCultureIgnoreCase))
        If pi.Any() Then
            Dim value = pi.FirstOrDefault().GetValue(currentEntity, Nothing)
            Return value
        End If
        Return Nothing
    End Function

    Public Sub SetEntityValue(ByRef currentEntity As Object, ByVal propertyName As String, ByRef newValue As Object)
        SetEntityValue(currentEntity, currentEntity.GetType.GetProperty(propertyName), newValue)
    End Sub



    Public Sub SetEntityValue(ByRef currentEntity As Object, ByRef pi As System.Reflection.PropertyInfo, ByRef newValue As Object)




        If newValue Is DBNull.Value OrElse newValue Is Nothing OrElse String.IsNullOrEmpty(newValue.ToString()) Then
            newValue = Nothing
            pi.SetValue(currentEntity, newValue, Nothing)
            Return
        End If

        If newValue.GetType().Equals(pi.PropertyType) Then
            pi.SetValue(currentEntity, newValue, Nothing)
            Return
        End If



        If Not newValue Is Nothing Then

            Dim tname As String
            Dim isNullable As Boolean = False
            If pi.PropertyType.IsGenericType AndAlso pi.PropertyType.GetGenericTypeDefinition() Is GetType(Nullable(Of )) Then

                Select Case True

                    Case pi.PropertyType.FullName.ToLower.Contains("uint16")
                        tname = "uint16"
                        isNullable = True
                    Case pi.PropertyType.FullName.ToLower.Contains("uint32")
                        tname = "uint32"
                        isNullable = True
                    Case pi.PropertyType.FullName.ToLower.Contains("uint64")
                        tname = "uint64"
                        isNullable = True
                    Case pi.PropertyType.FullName.ToLower.Contains("date")
                        tname = "date"
                        isNullable = True
                    Case pi.PropertyType.FullName.ToLower.Contains("int16")
                        tname = "int16"
                        isNullable = True
                    Case pi.PropertyType.FullName.ToLower.Contains("int32")
                        tname = "int32"
                        isNullable = True
                    Case pi.PropertyType.FullName.ToLower.Contains("int64")
                        tname = "int64"
                        isNullable = True
                    Case pi.PropertyType.FullName.ToLower.Contains("double")
                        tname = "double"
                        isNullable = True
                    Case pi.PropertyType.FullName.ToLower.Contains("float")
                        tname = "float"
                        isNullable = True
                    Case pi.PropertyType.FullName.ToLower.Contains("byte")
                        tname = "byte"
                        isNullable = True
                    Case pi.PropertyType.FullName.ToLower.Contains("bool")
                        tname = "bool"
                        isNullable = True
                    Case pi.PropertyType.FullName.ToLower.Contains("guid")
                        tname = "guid"
                        isNullable = True
                    Case Else
                        tname = pi.PropertyType.FullName.ToLower

                        isNullable = True
                End Select
            Else
                tname = pi.PropertyType.Name.ToLower

            End If






            Select Case tname

                ' pi.SetValue(currentEntity, CStr(newValue), Nothing)
                Case "string"
                    If newValue Is DBNull.Value Then
                        pi.SetValue(currentEntity, Nothing, Nothing)
                        Exit Select
                    End If

                    If newValue.GetType().Name.Equals("guid", StringComparison.InvariantCultureIgnoreCase) Then
                        pi.SetValue(currentEntity, newValue.ToString(), Nothing)
                        Exit Select
                    End If

                    pi.SetValue(currentEntity, CStr(newValue), Nothing)


                Case "int32", "integer"

                    If IsNumeric(newValue) Then

                        pi.SetValue(currentEntity, IIf(isNullable, convertToNullable(Of Int32)(newValue), CInt(newValue)), Nothing)
                    Else
                        Throw New ApplicationException("Tipo de dato no valido" & pi.Name)
                    End If
                Case "uinteger", "uint32"

                    If IsNumeric(newValue) Then
                        If Not isNullable Then
                            pi.SetValue(currentEntity, CUInt(newValue), Nothing)
                        Else
                            Dim n As Nullable(Of UInt32)
                            n = CUInt(newValue)
                            pi.SetValue(currentEntity, n, Nothing)
                        End If
                    Else
                        Throw New ApplicationException("Tipo de dato no valido" & pi.Name)
                    End If

                Case "double"
                    If IsNumeric(newValue) Then
                        If isNullable Then
                            Dim n As Double?

                            Dim conv As Double
                            Dim uiSep = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator
                            If uiSep = "," AndAlso newValue IsNot Nothing Then
                                newValue = CStr(newValue).Replace("."c, ","c)
                            End If

                            If Double.TryParse(newValue, conv) Then
                                n = conv
                            Else
                                n = convertToNullable(Of Double)(newValue)
                            End If



                            pi.SetValue(currentEntity, n, Nothing)
                        Else
                            Dim uiSep = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator
                            If uiSep = "," AndAlso newValue IsNot Nothing Then
                                newValue = CStr(newValue).Replace("."c, ","c)
                            End If
                            pi.SetValue(currentEntity, CDbl(newValue), Nothing)
                        End If
                    Else
                        Throw New ApplicationException("Tipo de dato no valido" & pi.Name)
                    End If

                Case "long", "int64"
                    If IsNumeric(newValue) Then

                        pi.SetValue(currentEntity, IIf(isNullable, CType(newValue, Nullable(Of Int64)), Convert.ToInt64(newValue)), Nothing)
                    Else
                        Throw New ApplicationException("Tipo de dato no valido" & pi.Name)
                    End If
                Case "ulong", "uint64"
                    If IsNumeric(newValue) Then
                        pi.SetValue(currentEntity, IIf(isNullable, CType(newValue, Nullable(Of UInt64)), Convert.ToUInt64(newValue)), Nothing)
                    Else
                        Throw New ApplicationException("Tipo de dato no valido" & pi.Name)
                    End If


                Case "decimal"
                    If IsNumeric(newValue) Then
                        Dim uiSep = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator
                        If uiSep = "," AndAlso newValue IsNot Nothing Then
                            newValue = CStr(newValue).Replace("."c, ","c)
                        End If
                        pi.SetValue(currentEntity, IIf(isNullable, CType(CDec(newValue), Nullable(Of Decimal)), CDec(newValue)), Nothing)
                    Else
                        Throw New ApplicationException("Tipo de dato no valido" & pi.Name)
                    End If

                Case "date"
                    If IsDate(newValue) Then
                        Try
                            pi.SetValue(currentEntity, CDate(newValue), Nothing)
                        Catch ex As Exception
                            pi.SetValue(currentEntity, Now, Nothing)
                        End Try
                    End If

                Case "int16", "short"

                    If IsNumeric(newValue) Then
                        pi.SetValue(currentEntity, IIf(isNullable, CType(newValue, Nullable(Of Int16)), Convert.ToInt16(newValue)), Nothing)
                    Else
                        Throw New ApplicationException("Tipo de dato no valido" & pi.Name)
                    End If
                Case "uint16", "ushort"

                    If IsNumeric(newValue) Then
                        pi.SetValue(currentEntity, IIf(isNullable, CType(newValue, Nullable(Of UInt16)), Convert.ToUInt16(newValue)), Nothing)
                    Else
                        Throw New ApplicationException("Tipo de dato no valido" & pi.Name)
                    End If

                Case "float", "single"
                    Dim uiSep = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator
                    If uiSep = "," AndAlso newValue IsNot Nothing Then
                        newValue = CStr(newValue).Replace("."c, ","c)
                    End If
                    If IsNumeric(newValue) Then
                        pi.SetValue(currentEntity, Single.Parse(newValue), Nothing)
                    Else
                        Throw New ApplicationException("Tipo de dato no valido" & pi.Name)
                    End If



                Case "bool", "boolean"
                    If Not newValue Is Nothing AndAlso (CStr(newValue) = "1" OrElse newValue.ToString.ToLower = "true" OrElse newValue.ToString.ToLower = "on") Then
                        pi.SetValue(currentEntity, True, Nothing)
                    Else
                        pi.SetValue(currentEntity, False, Nothing)
                    End If

                Case "sbyte", "sbyte"
                    pi.SetValue(currentEntity, Convert.ToSByte(newValue), Nothing)
                Case "byte", "byte"
                    pi.SetValue(currentEntity, Convert.ToByte(newValue), Nothing)
                Case "char", "byte"
                    pi.SetValue(currentEntity, Convert.ToChar(newValue), Nothing)
                Case Else
                    pi.SetValue(currentEntity, newValue, Nothing)

            End Select

        End If
    End Sub


    Public Function convertToNullable(Of T)(ByVal value As Object) As Object

        Dim newValue As Object
        If TypeOf value Is Decimal AndAlso GetType(T).Equals(GetType(Double)) Then
            newValue = System.Convert.ToDouble(value)
        ElseIf GetType(T).Equals(GetType(UInt32)) Then
            Dim n As Nullable(Of UInt32)
            n = CUInt(value)
            Return n
        ElseIf GetType(T).Equals(GetType(Int32)) Then
            Dim n As Nullable(Of Int32)
            n = CInt(value)
            Return n

        Else
            newValue = value
        End If

        Dim classType As Type = GetType(Nullable(Of ))
        Dim constructedType As Type = classType.MakeGenericType(GetType(T))
        Dim obj = Activator.CreateInstance(constructedType, newValue)

        'Dim convertedValue = System.Convert.ChangeType(newValue, constructedType)
        Return obj
        'obj.value = value
        'Return obj
    End Function

    Public Function GetNullableType(ByVal type As Type) As Type
        ' Use Nullable.GetUnderlyingType() to remove the Nullable<T> wrapper if type is already nullable.
        type = Nullable.GetUnderlyingType(type)
        If type.IsValueType Then
            Return GetType(Nullable(Of )).MakeGenericType(type)
        Else
            Return type
        End If
    End Function

    Public Function GetEntityClassInstance(Of T)() As T
        Return GetEntityClassInstance(GetType(T).FullName)
    End Function





    Public Function GetEntityClassInstance(ByVal entityName As String, Optional ByVal NoThrowExceptionAtNotFound As Boolean = False) As Object
        If entityName.IsNothing() Then
            Throw New ApplicationException("Debe explicitar el nombre de la clase")
        End If




        Dim tipo As Type

        Try

            Dim key As String = "GetEntityClassInstance." & entityName

            tipo = CacheMannager.getFromCache(Of Object, Type)(key)
            If Not tipo Is Nothing Then
                CacheMannager.SetInCache(Of Object, Type)(key, tipo, 500)
                Return Activator.CreateInstance(tipo)
            End If

            tipo = System.Type.GetType(entityName)

            If Not tipo Is Nothing Then
                CacheMannager.SetInCache(Of Object, Type)(key, tipo, 500)
                Return Activator.CreateInstance(tipo)
            End If


            Dim l = AppDomain.CurrentDomain.GetAssemblies()
            Dim ext As Exception = New ApplicationException("Clase no encontrada")
            If l.Any() Then
                Try
                    For Each item In l.ToArray()
                        Try
                            Dim gt = item.GetTypes().Where(Function(x) _
                                                          x.FullName.Equals(entityName, StringComparison.InvariantCultureIgnoreCase) _
                                                          OrElse x.Name.Equals(entityName, StringComparison.InvariantCultureIgnoreCase))
                            If gt.Any() Then
                                CacheMannager.SetInCache(Of Object, Type)(key, gt.FirstOrDefault(), 500)
                                Return Activator.CreateInstance(gt.FirstOrDefault())
                            End If
                        Catch
                        End Try
                    Next
                Catch dex As Exception
                    ext = dex
                End Try
            End If

            If NoThrowExceptionAtNotFound = False Then
                Throw ext
            End If


        Catch ex As Exception
            If NoThrowExceptionAtNotFound = False Then
                Throw ex
            End If
        End Try

        Return Nothing
    End Function


    ''' <summary>
    ''' Reads data reader into a list of entities
    ''' </summary>
    ''' <typeparam name="TEntity">The type of the entity.</typeparam>
    Public Class EntityFiller(Of TEntity)
        'must have public parameterless constructor
        'a dictionary of all properties on the entity
        Private _properties As IDictionary(Of String, MemberInfo) = New Dictionary(Of String, MemberInfo)()
        Private _type As Type = GetType(TEntity)

        ''' <summary>
        ''' Reads data reader into a list of entities
        ''' </summary>
        ''' <param name="dataReader">The data reader.</param>
        ''' <returns>A list of the specified entities</returns>
        Public Function Fill(ByVal dataReader As IDataReader) As IList(Of TEntity)
            If dataReader Is Nothing Then
                Throw New ArgumentNullException("dataReader")
            End If

            Dim result = New List(Of TEntity)()
            If dataReader Is Nothing Then
                Return result
            End If

            'a list of dictionaries for each row
            Dim rows = New List(Of IDictionary(Of String, Object))()
            While dataReader.Read()
                rows.Add(ReadRow(dataReader))
            End While

            'close the dataReader
            dataReader.Close()

            'use the list of dictionaries
            For Each row In rows
                result.Add(BuildEntity(row))
            Next

            Return result
        End Function

        Private Function ReadRow(ByVal record As IDataRecord) As IDictionary(Of String, Object)
            Dim row = New Dictionary(Of String, Object)()
            For i As Integer = 0 To record.FieldCount - 1
                row.Add(record.GetName(i), record.GetValue(i))
            Next
            Return row
        End Function

        Private Function BuildEntity(ByVal row As IDictionary(Of String, Object)) As TEntity
            Dim entity = GetEntityClassInstance(Of TEntity)()

            For Each item In row
                Dim key = item.Key
                Dim value = item.Value
                If value Is DBNull.Value Then
                    value = Nothing
                End If
                'may be DBNull
                SetProperty(key, entity, value)
            Next

            Return entity
        End Function

#Region "Reflect into entity"
        Private Sub SetProperty(ByVal key As String, ByVal entity As TEntity, ByVal value As Object)
            'first try dictionary
            If _properties.ContainsKey(key) Then
                SetPropertyFromDictionary(_properties(key), entity, value)
                Return
            End If

            'otherwise (should be first time only) reflect it out
            'first look for a writeable public property of any case
            Dim [property] = _type.GetProperty(key, BindingFlags.Instance Or BindingFlags.[Public] Or BindingFlags.IgnoreCase)
            If [property] IsNot Nothing AndAlso [property].CanWrite Then
                _properties.Add(key, [property])
                SetEntityValue(entity, [property], value)
                '[property].SetValue(entity, value, Nothing)
                Return
            End If

            ''look for a nonpublic field with the standard _ prefix
            'Dim field = _type.GetField("_" & key, BindingFlags.Instance Or BindingFlags.NonPublic Or BindingFlags.IgnoreCase)
            '_properties.Add(key, field)
            'field.SetValue(entity, value)
        End Sub

        Private Sub SetPropertyFromDictionary(ByVal member As MemberInfo, ByVal entity As TEntity, ByVal value As Object)
            Dim [property] = TryCast(member, PropertyInfo)
            If [property] IsNot Nothing Then
                SetEntityValue(entity, [property], value)
                '[property].SetValue(entity, value, Nothing)
            End If
            Dim field = TryCast(member, FieldInfo)
            If field IsNot Nothing Then
                field.SetValue(entity, value)
            End If
        End Sub
#End Region


    End Class


End Module
